#include <unistd.h>
#include <iostream>

#include "../../libpland/lib/coreploader.hpp"
#include "../../libpland/lib/coreqmanager.hpp"
#include "../../libpland/lib/coresettings.hpp"
#include "../../libpland/lib/coreutil.hpp"

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread.hpp>
#include <vector>
#include <iostream>

#include "TCanvas.h"
#include "TStyle.h"
#include "TGraph.h"
#include "TApplication.h"
#include "TAxis.h"
#include "TPad.h"
#include "TF1.h"

#define QUEUE_DEPTH 100
#define ITTERATIONS 2
#define FFT_PACKET_SIZE 300
using namespace std;
using namespace boost::posix_time;

bool run;
long push_performance;
long pop_performance;
vector<long> parallel_performance;


vector<long> serial_performance;
vector<long> latency_performance;
vector<float> sleep_per_op;

TCanvas * c1;
bool gui;
int n_cores;
CoreC_Data * data_item;
CoreC_Data_Header* data_header;

void queueTest()
{
    CoreC_DQueue *q = new CoreC_DQueue(QUEUE_DEPTH);
    //Single threaded
    ptime now = microsec_clock::local_time();
    ptime later = microsec_clock::local_time();
    time_duration push = now - now;
    time_duration pop = now - now;
    for(int i = 0; i < ITTERATIONS; i++)
    {
        data_item = new CoreC_Data();
        now = microsec_clock::local_time();
        for(int j = 0; j < QUEUE_DEPTH; j++)
        {
            q->push(data_item);
        }
        later = microsec_clock::local_time();
        push += later - now;

        now = microsec_clock::local_time();
        for(int j = 0; j < QUEUE_DEPTH; j++)
        {
            data_item = q->pop();
        }
        later = microsec_clock::local_time();
        delete data_item;
        pop += later - now;
    }
    push_performance = ITTERATIONS*QUEUE_DEPTH/(push.total_microseconds()*1.0e-6);
    pop_performance = ITTERATIONS*QUEUE_DEPTH/(pop.total_microseconds()*1.0e-6);
    cout << "Queue throughput single-threaded:" << endl;
    cout << string("Push: \t") + utill_to_string(push_performance) + "\tPackets/second" << endl;
    cout << string("Pop: \t") + utill_to_string(pop_performance) + "\tPackets/second" << endl;
    delete q;
}
void threadTest(int cd)
{
    parallel_performance.clear();
    vector<CoreC_Plugin*> p_list;
    CoreC_DQueue *qin = new CoreC_DQueue(QUEUE_DEPTH);
    CoreC_DQueue *qout = new CoreC_DQueue(QUEUE_DEPTH);
    cout << string("Parallel scaling tests:") << endl;
    for(int proc = 1; proc < n_cores*2 +1; proc++)
    {
        CoreC_Plugin* plugin = CoreC_PLoader::loadPlugin("./libplugsin.so", proc);
        p_list.push_back(plugin);
        plugin->getUserPlugin()->load_queues(qin, qout);
        ptime now = microsec_clock::local_time();
        ptime later = microsec_clock::local_time();
        time_duration dur= now - now;
        for(int i = 0; i < ITTERATIONS; i++)
        {
            for(int j = 0; j< p_list.size(); j++)
            {
                p_list[j]->stop();
            }
            for(int j = 0; j < QUEUE_DEPTH; j++)
            {
                data_item = new CoreC_Data();
                data_header = new CoreC_Data_Header;
                double *data = new double[FFT_PACKET_SIZE];
                data_header->_size = FFT_PACKET_SIZE*sizeof(double);
                data_header->_tag = i*j*proc;
                data_item->load_data(data,data_header);
                qin->push(data_item);
            }
            for(int j = 0; j< p_list.size(); j++)
            {
                p_list[j]->start();
            }
            now = microsec_clock::local_time();

            for(int j = 0; j < QUEUE_DEPTH; j++)
            {
                data_item = qout->pop();
                delete data_item;
            }
            later = microsec_clock::local_time();
            dur += later - now;
        }
        parallel_performance.push_back(ITTERATIONS*QUEUE_DEPTH/(dur.total_microseconds()*1.0e-6));
        cout <<utill_to_string(proc) + " Threads:\t" + utill_to_string(parallel_performance[proc - 1])  + "\tPackets/second" << endl;
    }
    for(int i = 0; i< p_list.size(); i++)
    {
        p_list[i]->stop();
    }
    for(int i = 0; i<p_list.size(); i++)
    {
        delete p_list[i];
    }
    delete qin;
    delete qout;
    if(gui)
    {
        float *x = (float*)malloc(sizeof(float)*(parallel_performance.size()+1));
        float *y = (float*)malloc(sizeof(float)*(parallel_performance.size()+1));
        x[0] = 0;
        y[0] = 0;
        for(int i =0; i< parallel_performance.size(); i++)
        {
            y[i+1] = parallel_performance[i]/(i+1);
            x[i+1] = i+1;
        }
        TGraph *gr = new TGraph(parallel_performance.size()+1, x, y);
        gr->SetTitle("Parallel Thread Performance");
        gr->SetLineColor(38);
        gr->SetLineWidth(2);
        gr->SetMarkerColor(50);
        gr->SetMarkerSize(1.5);
        gr->SetMarkerStyle(21);
        gr->SetTitle("Parallel Thread Performance");
        gr->GetXaxis()->SetTitle("Number of threads");
        gr->GetYaxis()->SetTitle("Throughput per thread(Packets/second)");
        c1->cd(cd);
        gr->SetFillColor(38);
        gr->Draw("AB");
        free(x);
        free(y);
    }
    if(gui)
    {
        float *x = (float*)malloc(sizeof(float)*(parallel_performance.size()));
        float *y = (float*)malloc(sizeof(float)*(parallel_performance.size()));
        for(int i =0; i< parallel_performance.size(); i++)
        {
            y[i+1] = parallel_performance[i];
            x[i+1] = i+1;
        }
        TGraph *gr = new TGraph(parallel_performance.size(), x, y);
        gr->SetLineColor(38);
        gr->SetLineWidth(2);
        gr->SetMarkerColor(50);
        gr->SetMarkerSize(1.5);
        gr->SetMarkerStyle(21);
        gr->SetTitle("Parallel Total Performance");
        gr->GetXaxis()->SetTitle("Number of threads");
        gr->GetYaxis()->SetTitle("Throughput (Packets/second)");
        c1->cd(cd+1);
        gr->Draw("AL*");
        free(x);
        free(y);
    }
}
void latencyTest(int cd)
{
    latency_performance.clear();
    //Latency stuff serial
    vector< CoreC_DQueue * > q_list;
    vector<CoreC_Plugin * > p_list;
    CoreC_DQueue* qin =  new CoreC_DQueue(QUEUE_DEPTH);
    q_list.push_back(qin);
    cout <<  "Serial Latency test:" << endl;
    for(int proc = 1; proc < n_cores*4 +1; proc++)
    {
        CoreC_Plugin* plugin = CoreC_PLoader::loadPlugin("./libplugsin.so", proc);
        p_list.push_back(plugin);
        CoreC_DQueue* last = new CoreC_DQueue(QUEUE_DEPTH);
        plugin->getUserPlugin()->load_queues(q_list.back(), last);
        q_list.push_back(last);
        plugin->start();
        ptime now = microsec_clock::local_time();
        ptime later = microsec_clock::local_time();
        time_duration dur= now - now;
        for(int i = 0; i < ITTERATIONS*10; i++)
        {
            data_item = new CoreC_Data();
            data_header = new CoreC_Data_Header;
            double *data = new double[FFT_PACKET_SIZE];
            data_header->_size = FFT_PACKET_SIZE*sizeof(double);
            data_item->load_data(data,data_header);
            qin->push(data_item);
            now = microsec_clock::local_time();
            delete last->pop();
            later = microsec_clock::local_time();
            dur += later - now;
        }
        latency_performance.push_back(dur.total_microseconds()/ITTERATIONS/10/1000.0);
        cout << proc << " Threads:\t"<< latency_performance.back() << "\tMillisecond latency" << endl;
    }
    for(int i = 0; i< p_list.size(); i++)
    {
        p_list[i]->stop();
    }
    for(int i = 0; i<p_list.size(); i++)
    {
        delete p_list[i];
    }
    for(int i = 0; i<q_list.size(); i++)
    {
        delete q_list[i];
    }
    if(gui)
    {
        float *x = (float*)malloc(sizeof(float)*latency_performance.size());
        float *y = (float*)malloc(sizeof(float)*latency_performance.size());
        for(int i =0; i< latency_performance.size(); i++)
        {
            y[i] = latency_performance[i];
            x[i] = i+1;
        }
        TGraph *gr = new TGraph(latency_performance.size(), x, y);
        TStyle *style = new TStyle();

        gr->SetTitle("Latency Performance");
        gr->Fit("pol1", "Q");
        gr->SetMarkerColor(50);
        gr->SetMarkerSize(1.5);
        gr->SetMarkerStyle(21);
        gr->GetXaxis()->SetTitle("Number of threads");
        gr->GetYaxis()->SetTitle("Latency (ms)");
        c1->cd(cd);

        gr->Draw("A*");
        free(x);
        free(y);
    }
}
void serialTest(int cd)
{
    serial_performance.clear();
    sleep_per_op.clear();
    //Throughput stuff serial
    vector< CoreC_DQueue * > q_list;
    vector<CoreC_Plugin * > p_list;
    CoreC_DQueue* qin =  new CoreC_DQueue(QUEUE_DEPTH);
    qin->setProfile(true);
    q_list.push_back(qin);
    cout <<  "Serial throughput test:" << endl;
    for(int proc = 1; proc < n_cores*2 +1; proc++)
    {
        CoreC_Plugin* plugin = CoreC_PLoader::loadPlugin("./libplugsin.so", proc);
        p_list.push_back(plugin);
        CoreC_DQueue* last = new CoreC_DQueue(QUEUE_DEPTH);
        last->setProfile(true);
        plugin->getUserPlugin()->load_queues(q_list.back(), last);
        q_list.push_back(last);
        ptime now = microsec_clock::local_time();
        ptime later = microsec_clock::local_time();
        time_duration dur= now - now;
        for(int plu = 0; plu< p_list.size(); plu++)
            p_list[plu]->stop();
        for(int i = 0; i< proc;i++)
            q_list.at(i)->resetCounters();
        for(int i = 0; i < ITTERATIONS; i++)
        {
            for(int j = 0; j <QUEUE_DEPTH; j++)
            {
                data_item = new CoreC_Data();
                data_header = new CoreC_Data_Header;
                double *data = new double[FFT_PACKET_SIZE];
                data_header->_size = FFT_PACKET_SIZE*sizeof(double);
                data_item->load_data(data,data_header);
                qin->push(data_item);
            }
            bool first = true;
            for(int plu = 0; plu< p_list.size(); plu++)
                p_list[plu]->start();
            for(int j = 0; j <QUEUE_DEPTH; j++)
            {
                delete last->pop();
                if(first)
                {
                    first = false;
                    now = microsec_clock::local_time();
                }
            }
            later = microsec_clock::local_time();
            dur += later - now;
        }
        int ops = 0;
        boost::posix_time::time_duration sleepDuration = boost::posix_time::microseconds(0);
        for(int i = 0; i< proc; i++)
        {
            ops += q_list.at(i)->getStatus().ops;
            sleepDuration += q_list.at(i)->getStatus().wait_durration;
        }
        serial_performance.push_back(ITTERATIONS*QUEUE_DEPTH/(dur.total_microseconds()*1.0e-6));
        sleep_per_op.push_back((1.0*sleepDuration.total_microseconds())/(1.0*ops));
        cout << proc << " Threads:\t"<< serial_performance.back() << "\t Packets/seconds, sleep per op " << sleep_per_op.back() << "us/op"<< endl;
    }
    for(int i = 0; i< p_list.size(); i++)
    {
        p_list[i]->stop();
    }
    for(int i = 0; i<p_list.size(); i++)
    {
        delete p_list[i];
    }
    for(int i = 0; i<q_list.size(); i++)
    {
        delete q_list[i];
    }
    if(gui)
    {
        float *x = (float*)malloc(sizeof(float)*serial_performance.size());
        float *y = (float*)malloc(sizeof(float)*serial_performance.size());
        for(int i =0; i< serial_performance.size(); i++)
        {
            y[i] = serial_performance[i];
            x[i] = i+1;
        }
        {
            TGraph *gr = new TGraph(serial_performance.size(), x, y);
            gr->SetTitle("Serial Throughput Performance");
            gr->SetLineColor(38);
            gr->SetLineWidth(2);
            gr->SetMarkerColor(50);
            gr->SetMarkerSize(1.5);
            gr->SetMarkerStyle(21);
            gr->GetXaxis()->SetTitle("Number of threads");
            gr->GetYaxis()->SetTitle("Throughput (Packets/second)");
            c1->cd(cd);
            gr->Draw("AL*");
        }
        free(x);
        free(y);
    }

}

void threadProfiled(int cd)
{
    parallel_performance.clear();
    vector<CoreC_Plugin*> p_list;
    CoreC_DQueue *qin = new CoreC_DQueue(QUEUE_DEPTH);
    qin->setProfile(true);
    CoreC_DQueue *qout = new CoreC_DQueue(QUEUE_DEPTH);
    qout->setProfile(true);
    cout << string("Parallel scaling tests:") << endl;
    for(int proc = 1; proc < n_cores*2 +1; proc++)
    {
        CoreC_Plugin* plugin = CoreC_PLoader::loadPlugin("./libplugsin.so", proc);
        p_list.push_back(plugin);
        plugin->getUserPlugin()->load_queues(qin, qout);
        ptime now = microsec_clock::local_time();
        ptime later = microsec_clock::local_time();
        time_duration dur= now - now;
        for(int i = 0; i < ITTERATIONS; i++)
        {
            for(int j = 0; j< p_list.size(); j++)
            {
                p_list[j]->stop();
            }
            for(int j = 0; j < QUEUE_DEPTH; j++)
            {
                data_item = new CoreC_Data();
                data_header = new CoreC_Data_Header;
                double *data = new double[FFT_PACKET_SIZE];
                data_header->_size = FFT_PACKET_SIZE*sizeof(double);
                data_header->_tag = i*j*proc;
                data_item->load_data(data,data_header);
                qin->push(data_item);
            }
            for(int j = 0; j< p_list.size(); j++)
            {
                p_list[j]->start();
            }
            now = microsec_clock::local_time();

            for(int j = 0; j < QUEUE_DEPTH; j++)
            {
                data_item = qout->pop();
                delete data_item;
            }
            later = microsec_clock::local_time();
            dur += later - now;
        }
        parallel_performance.push_back(ITTERATIONS*QUEUE_DEPTH/(dur.total_microseconds()*1.0e-6));
        cout <<utill_to_string(proc) + " Threads:\t" + utill_to_string(parallel_performance[proc - 1])  + "\tPackets/second" << endl;
    }
    for(int i = 0; i< p_list.size(); i++)
    {
        p_list[i]->stop();
    }
    for(int i = 0; i<p_list.size(); i++)
    {
        delete p_list[i];
    }
    delete qin;
    delete qout;
    cout << parallel_performance.size() << endl;
    if(gui)
    {
        float *x = (float*)malloc(sizeof(float)*(parallel_performance.size()));
        float *y = (float*)malloc(sizeof(float)*(parallel_performance.size()));
        for(int i =0; i< parallel_performance.size(); i++)
        {
            y[i+1] = parallel_performance[i];
            x[i+1] = i+1;
        }
        TGraph *gr = new TGraph(parallel_performance.size(), x, y);
        gr->SetLineColor(38);
        gr->SetLineWidth(2);
        gr->SetMarkerColor(50);
        gr->SetMarkerSize(1.5);
        gr->SetMarkerStyle(21);
        gr->SetTitle("Parallel Total Performance(Profiled)");
        gr->GetXaxis()->SetTitle("Number of threads");
        gr->GetYaxis()->SetTitle("Throughput (Packets/second)");
        c1->cd(cd);
        gr->Draw("AL*");
        free(x);
        free(y);
    }
}

void threadUnprofiled(int cd)
{
    parallel_performance.clear();
    vector<CoreC_Plugin*> p_list;
    CoreC_DQueue *qin = new CoreC_DQueue(QUEUE_DEPTH);
    CoreC_DQueue *qout = new CoreC_DQueue(QUEUE_DEPTH);
    cout << string("Parallel scaling tests:") << endl;
    for(int proc = 1; proc < n_cores*2 +1; proc++)
    {
        CoreC_Plugin* plugin = CoreC_PLoader::loadPlugin("./libplugsin.so", proc);
        p_list.push_back(plugin);
        plugin->getUserPlugin()->load_queues(qin, qout);
        ptime now = microsec_clock::local_time();
        ptime later = microsec_clock::local_time();
        time_duration dur= now - now;
        for(int i = 0; i < ITTERATIONS; i++)
        {
            for(int j = 0; j< p_list.size(); j++)
            {
                p_list[j]->stop();
            }
            for(int j = 0; j < QUEUE_DEPTH; j++)
            {
                data_item = new CoreC_Data();
                data_header = new CoreC_Data_Header;
                double *data = new double[FFT_PACKET_SIZE];
                data_header->_size = FFT_PACKET_SIZE*sizeof(double);
                data_header->_tag = i*j*proc;
                data_item->load_data(data,data_header);
                qin->push(data_item);
            }
            for(int j = 0; j< p_list.size(); j++)
            {
                p_list[j]->start();
            }
            now = microsec_clock::local_time();

            for(int j = 0; j < QUEUE_DEPTH; j++)
            {
                data_item = qout->pop();
                delete data_item;
            }
            later = microsec_clock::local_time();
            dur += later - now;
        }
        parallel_performance.push_back(ITTERATIONS*QUEUE_DEPTH/(dur.total_microseconds()*1.0e-6));
        cout <<utill_to_string(proc) + " Threads:\t" + utill_to_string(parallel_performance[proc - 1])  + "\tPackets/second" << endl;
    }
    for(int i = 0; i< p_list.size(); i++)
    {
        p_list[i]->stop();
    }
    for(int i = 0; i<p_list.size(); i++)
    {
        delete p_list[i];
    }
    delete qin;
    delete qout;
    if(gui)
    {
        float *x = (float*)malloc(sizeof(float)*(parallel_performance.size()));
        float *y = (float*)malloc(sizeof(float)*(parallel_performance.size()));
        for(int i =0; i< parallel_performance.size(); i++)
        {
            y[i+1] = parallel_performance[i];
            x[i+1] = i+1;
        }
        TGraph *gr = new TGraph(parallel_performance.size(), x, y);
        gr->SetLineColor(38);
        gr->SetLineWidth(2);
        gr->SetMarkerColor(50);
        gr->SetMarkerSize(1.5);
        gr->SetMarkerStyle(21);
        gr->SetTitle("Parallel Total Performance(Unprofiled)");
        gr->GetXaxis()->SetTitle("Number of threads");
        gr->GetYaxis()->SetTitle("Throughput (Packets/second)");
        c1->cd(cd);
        gr->Draw("AL*");
        free(x);
        free(y);
    }
}

int main(int argc, char** argv)
{
    gui = 1;
    TApplication theApp("tapp", &argc, argv);
    c1 = new TCanvas("Metrics", "Metrics", 800, 600);

    if(gui)
        c1->Divide(1,2);
    n_cores = sysconf(_SC_NPROCESSORS_ONLN);
    ofstream myfile("log.txt");
    utill_set_log(&myfile);
    utill_log("Found " + utill_to_string(n_cores)  +" CPU cores.");

    threadUnprofiled(1);
    threadProfiled(2);
    if(gui)
    {
        theApp.Run();
    }
}
